package internal

import (
	"context"
	"fmt"
	"github.com/shopspring/decimal"
	"math"
	"stock/server/dao"
	"stock/server/dao/http/xueqiu"
	"stock/server/internal/pkg/notify"
	"stock/server/internal/pkg/stock"
	"stock/server/internal/pkg/stock/httputil"
	"stock/server/internal/pkg/stock/indicators"
	"stock/server/internal/pkg/stock/typeutil"

	"strconv"

	"sync"
	"time"
)

type (
	notifyHandler func(param, code string)
)

const (
	Realtime string = "3s" // 实时数据, 3 秒刷新一次
)

type Stock struct {
	Mux                      sync.Mutex
	ctx                      context.Context
	KlineMap                 map[string]map[string][]typeutil.Kline
	channelMap               map[string]chan typeutil.Kline
	registerNotifyHandlerMap map[string][]notifyHandler // 注册通知函数
	notifyMap                map[string]struct {
		count      int
		expireTime time.Time
	}

	CodeInfoMap map[string]dao.StockInfo
	DB          dao.IStockDB
}

func NewStock() *Stock {
	x := &xueqiu.Xueqiu{}
	//x := &sqlite.Sqlite{}
	//db, err := x.Open(sqlite.Option{FilePath: "/Users/flyele/Desktop/go_program/stock-server/db.sqlite"})
	//if err != nil {
	//	panic(err)
	//}
	_, _ = x.Open(nil)
	o := &Stock{
		DB: x,
	}

	// 股票信息
	_, o.CodeInfoMap, _ = o.DB.CodeInfo()

	// 通知
	o.notifyMap = make(map[string]struct {
		count      int
		expireTime time.Time
	})

	// 分时线(秒为key)
	o.KlineMap = make(map[string]map[string][]typeutil.Kline)

	// 协程, 5分钟线, 15分钟线, 30分钟线, 60分钟线
	o.channelMap = o.createChannel(Realtime, "5m", "15m", "30m", "60m")
	//o.channelMap = o.createChannel(Realtime, "1m")
	return o
}

func (o *Stock) StartUp(ctx context.Context) {
	//	req.DevMode()
	o.ctx = ctx

	// 注册通知函数
	o.registerNotifyHandlerMap = make(map[string][]notifyHandler)
	o.registerNotifyHandlerMap[Realtime] = []notifyHandler{o.realtimeHandle} // 实时
	o.registerNotifyHandlerMap["1m"] = []notifyHandler{o.minuteHandle}       // 1分钟
	o.registerNotifyHandlerMap["5m"] = []notifyHandler{o.minuteHandle}       // 5分钟
	o.registerNotifyHandlerMap["15m"] = []notifyHandler{o.minuteHandle}      // 15分钟
	o.registerNotifyHandlerMap["30m"] = []notifyHandler{o.minuteHandle}      // 30分钟
	o.registerNotifyHandlerMap["60m"] = []notifyHandler{o.minuteHandle}      // 60分钟

	var codeIds []string
	for code := range o.CodeInfoMap {
		codeIds = append(codeIds, code)
	}

	// 若为节假日, 则只返回最后一天的数据
	// 获取实时数据
	o.realtime(ctx, codeIds)

	// 根据实时数据计算指标
	o.minute(ctx, codeIds)
}

// 创建 channel
func (o *Stock) createChannel(params ...string) map[string]chan typeutil.Kline {
	m := make(map[string]chan typeutil.Kline)
	for _, param := range params {
		m[param] = make(chan typeutil.Kline, 1000)
	}
	return m
}

// 获取实时数据
func (o *Stock) realtime(ctx context.Context, codeIds []string) {
	go func() {
		timer := time.NewTimer(0 * time.Second)
		for {
			// 开市
			select {
			case <-ctx.Done():
				return
			case <-timer.C:
				// 休市
				if !stock.IsMarketOpen() {
					// 为了兼容非交易时间启动程序时, 没有获取数据
					if len(o.KlineMap) > 0 {
						// 重置时间
						timer.Reset(3000 * time.Millisecond)
						continue
					}
				}

				// 获取实时数据
				m, err := httputil.Tencent{}.Realtime(codeIds)
				if err != nil {
					panic(err)
				}

				// 分时数据 3 秒刷新一次
				if _, ok := o.KlineMap[Realtime]; !ok {
					o.KlineMap[Realtime] = make(map[string][]typeutil.Kline)
				}

				for code, kline := range m {
					// 通知各协程处理数据
					for _, channel := range o.channelMap {
						channel <- kline
					}

					o.Mux.Lock()
					o.KlineMap[Realtime][code] = append(o.KlineMap[Realtime][code], kline)
					o.Mux.Unlock()

					// 通知处理函数
					for _, fn := range o.registerNotifyHandlerMap[Realtime] {
						fn(Realtime, code)
					}
				}

				// 重置时间, 是用毫秒是为了防止时间不够精确,+200 是为了给腾讯接口留出时间
				// 当前时间 % 3s = n, n 距离下个3s已过去的时间
				n := 3200 - (time.Now().UnixMilli() % 3000)
				timer.Reset(time.Duration(n) * time.Millisecond)
			}
		}
	}()
}

// 分钟线
func (o *Stock) minute(ctx context.Context, codeIds []string) {
	for timeParam, channel := range o.channelMap {
		if timeParam == Realtime { // 分钟图不监听实时数据
			continue
		}

		go func(timeParam string, channel chan typeutil.Kline) { // 获取当前分钟线的最近 420 条数据
			second := o.toSecond(timeParam)
			m, _ := httputil.Tencent{}.Minute(second/60, codeIds)

			o.KlineMap[timeParam] = m
			for {
				select {
				case <-ctx.Done():
					return
				case kline := <-channel: // 处理实时数据
					// 追加
					if kline.Timestamp.Unix()%int64(second) < 3 {
						o.KlineMap[timeParam][kline.Symbol] = append(o.KlineMap[timeParam][kline.Symbol], kline)
						continue
					}

					// 对比
					// 最后一条数据
					last := o.KlineMap[timeParam][kline.Symbol][len(o.KlineMap[timeParam][kline.Symbol])-1]
					// 最高价
					last.HighPrice = max(kline.HighPrice, last.HighPrice)
					// 最低价
					last.LowPrice = min(kline.LowPrice, last.LowPrice)
					// 最新价
					last.ClosePrice = kline.ClosePrice

					// 重新赋值
					o.KlineMap[timeParam][kline.Symbol][len(o.KlineMap[timeParam][kline.Symbol])-1] = last

					// 重新计算指标
					o.KlineMap[timeParam][kline.Symbol] = indicators.MA(o.KlineMap[timeParam][kline.Symbol], []int{30, 60})
					o.KlineMap[timeParam][kline.Symbol] = indicators.CCI(o.KlineMap[timeParam][kline.Symbol], 14)

					for _, fn := range o.registerNotifyHandlerMap[timeParam] {
						fn(timeParam, kline.Symbol)
					}
				}
			}
		}(timeParam, channel)
	}
}

// 实时
func (o *Stock) realtimeHandle(_ string, code string) {
	o.Mux.Lock()
	klines := o.KlineMap[Realtime][code]
	o.Mux.Unlock()

	// 5分钟之内
	if len(klines) < 100 {
		return
	}

	last := klines[len(klines)-1]

	// 均线
	var sum float64
	minN := last.ClosePrice
	maxN := last.ClosePrice
	for i := len(klines) - 1; i >= 0; i-- {
		sum += klines[i].ClosePrice
		if i > len(klines)-60 {
			minN = min(klines[i].ClosePrice, minN)
			maxN = max(klines[i].ClosePrice, maxN)
		}
	}

	// 保留三位小数
	avg := math.Round(sum/float64(len(klines))*1e3) / 1e3
	for i := len(klines) - 1; i >= 0; i-- {
		f := (last.ClosePrice - klines[i].ClosePrice) / klines[i].ClosePrice
		if math.Abs(f) > 0.005 {
			s := "涨幅"
			if f < 0 {
				s = "跌幅"
			}
			o.notifyPush(fmt.Sprintf("%s.%s", s, code), fmt.Sprintf("【分时图】%s", o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("【%s】超过 0.5%% 最新涨幅: %s%%\n 最新价: %s    均线: %s", s, decimal.NewFromFloatWithExponent(last.IncreasePercentage, -2), decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(avg, -3)))
		}
	}

	//secondLast := klines[len(klines)-2]
	//if secondLast.ClosePrice < avg && last.ClosePrice > avg {
	//	// 上穿
	//	o.notifyPush(fmt.Sprintf("%s.%s", "上穿均线", code), fmt.Sprintf("【分时图】%s", o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("【上穿均线】, 最新价: %s, 均线: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(avg, -3)))
	//}
	//
	//if secondLast.ClosePrice > avg && last.ClosePrice < avg {
	//	// 下穿
	//	o.notifyPush(fmt.Sprintf("%s.%s", "下穿均线", code), fmt.Sprintf("【分时图】%s", o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("【下穿均线】, 最新价: %s, 均线: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(avg, -3)))
	//}
}

// 分钟
func (o *Stock) minuteHandle(param, code string) {
	o.Mux.Lock()
	klines := o.KlineMap[param][code]
	o.Mux.Unlock()

	if len(klines) > 2 {
		return
	}

	last := klines[len(klines)-1]
	secondLast := klines[len(klines)-2]
	// CCI 超250
	if secondLast.CCI < 250 && last.CCI >= 250 {
		o.notifyPush(fmt.Sprintf("%s.%s.CCI", param, code), fmt.Sprintf("【CCI.%s】%s", param, o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("CCI 超250, 最新价: %s, CCI: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(last.CCI, -1)))
	}

	// 上穿30,60
	if secondLast.ClosePrice < secondLast.MA[30] && last.ClosePrice > last.MA[30] {
		o.notifyPush(fmt.Sprintf("%s.%s.MA30", param, code), fmt.Sprintf("【MA30.%s】%s", param, o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("上穿30, 最新价: %s, MA30: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(last.MA[30], -3)))
	}
	if secondLast.ClosePrice < secondLast.MA[60] && last.ClosePrice > last.MA[60] {
		o.notifyPush(fmt.Sprintf("%s.%s.MA60", param, code), fmt.Sprintf("【MA60.%s】%s", param, o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("上穿60, 最新价: %s, MA60: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(last.MA[60], -3)))
	}

	// 下穿30,60
	if secondLast.ClosePrice > secondLast.MA[30] && last.ClosePrice < last.MA[30] {
		o.notifyPush(fmt.Sprintf("%s.%s.MA30", param, code), fmt.Sprintf("【MA30.%s】%s", param, o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("下穿30, 最新价: %s, MA30: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(last.MA[30], -3)))
	}
	if secondLast.ClosePrice > secondLast.MA[60] && last.ClosePrice < last.MA[60] {
		o.notifyPush(fmt.Sprintf("%s.%s.MA60", param, code), fmt.Sprintf("【MA60.%s】%s", param, o.CodeInfoMap[code].Abbreviation), fmt.Sprintf("下穿60, 最新价: %s, MA60: %s", decimal.NewFromFloatWithExponent(last.ClosePrice, -3), decimal.NewFromFloatWithExponent(last.MA[60], -3)))
	}
}

// 转换为秒
// 3s => 3, 5m => 300, 15m => 900, 30m => 1800, 60m => 3600
func (o *Stock) toSecond(param string) (second int) {
	if len(param) == 0 {
		panic("param is empty")
	}

	// 秒
	if param[len(param)-1] == 's' {
		second, _ = strconv.Atoi(param[:len(param)-1])
		return second
	}

	// 分钟
	if param[len(param)-1] == 'm' {
		minute, _ := strconv.Atoi(param[:len(param)-1])
		return minute * 60
	}

	panic("param is invalid")
}

func (o *Stock) notifyPush(notifyCode, title, message string) {
	fmt.Println(title, message)
	item, ok := o.notifyMap[notifyCode]
	if ok {
		if item.expireTime.Before(time.Now()) {
			item.count = 0
		}

		if item.count >= 3 {
			return
		}
	}
	_ = notify.MacNotify{}.Push(title, message)

	o.notifyMap[notifyCode] = struct {
		count      int
		expireTime time.Time
	}{
		count:      item.count + 1,
		expireTime: time.Now().Add(60 * time.Second),
	}
}

func (o *Stock) RealtimeListen() typeutil.Kline {
	return <-o.channelMap[Realtime]
}
